﻿
using MyFloridaHouse.libSvKMZ.KML;
using System;
using Microsoft.Maps.MapControl;
using System.Windows.Media;
using System.Collections.Generic;
using MyFloridaHouse.ServiceConn.DistrBldrSvcRef;
using MyFloridaHouse.libSvBingMapsMultiShape;
using MyFloridaHouse.libSvCommonUtilities;
using System.Windows;


namespace MyFloridaHouse.libSvOpenDistricts.Districts
{

    public static class DistrictPlanDataContract
    {
        const string LatLongFormatString = "####.#####";

        public static kml BuildKML(DistrictPlan districtPlan)
        {
            kml newKml = new kml();
            newKml.Items = new kmlDocument[1];

            // plan info
            newKml.Items[0] = new kmlDocument();
            newKml.Items[0].name = districtPlan.districtPlanName;
            newKml.Items[0].atomAuthor = new kmlDocumentAtomAuthor();
            newKml.Items[0].atomAuthor.name = "Florida House of Representatives - MyDistrictBuilder";
            newKml.Items[0].atomLink = new kmlDocumentAtomLink();
            newKml.Items[0].atomLink.href = "http://www.floridaredistricting.gov";
            newKml.Items[0].description = districtPlan.districtPlanDescription;
            newKml.Items[0].NumberofDistricts = districtPlan.districtCount.ToString();
            newKml.Items[0].plantype = districtPlan.districtPlanTypeName;

            newKml.Items[0].Placemark = new kmlDocumentPlacemark[districtPlan.districtCount];
            for (int districtID = 1; districtID <= districtPlan.districtCount; districtID++)
            {
                int geometryCount = districtPlan.districtList[districtID].districtMultiMapShape.Vertices.Count;
                if (geometryCount > 0)
                {
                    kmlDocumentPlacemark placemark = new kmlDocumentPlacemark();

                    placemark.name = districtID.ToString();
                    placemark.open = "0"; // tag is closed by default

                    placemark.description = "<![CDATA[<b>2010 Census Redistricting Data</b><br/>Need to add data items here]]>";

                    placemark.Style = new kmlDocumentPlacemarkStyle[1];
                    placemark.Style[0] = new kmlDocumentPlacemarkStyle();

                    placemark.Style[0].LineStyle = new kmlDocumentPlacemarkStyleLineStyle[1];
                    placemark.Style[0].LineStyle[0] = new kmlDocumentPlacemarkStyleLineStyle();
                    placemark.Style[0].LineStyle[0].color = "5f000000";
                    placemark.Style[0].LineStyle[0].width = "2";


                    placemark.Style[0].PolyStyle = new kmlDocumentPlacemarkStylePolyStyle[1];
                    placemark.Style[0].PolyStyle[0] = new kmlDocumentPlacemarkStylePolyStyle();
                    placemark.Style[0].PolyStyle[0].fill = "1";
                    placemark.Style[0].PolyStyle[0].color = DistrictColors.GetColorFromIndex(districtID).ToString();
                    placemark.Style[0].PolyStyle[0].outline = "1";

                    placemark.Style[0].IconStyle = new kmlDocumentPlacemarkStyleIconStyle();
                    placemark.Style[0].IconStyle.Icon = " ";

                    placemark.ExtendedData = new kmlDocumentPlacemarkExtendedData();
                    placemark.ExtendedData.hasHole = districtPlan.districtList[districtID].hasHole;
                    placemark.ExtendedData.isLocked = districtPlan.districtList[districtID].isLocked;
                    placemark.ExtendedData.isSelected = districtPlan.districtList[districtID].isSelected;
                    placemark.ExtendedData.isContiguous = districtPlan.districtList[districtID].isContiguous;
                    placemark.ExtendedData.targetPopulation = districtPlan.districtList[districtID].targetPopulation;

                    // write district bounding box
                    placemark.ExtendedData.latLonAltBox = new kmlLatLonAltBox();
                    placemark.ExtendedData.latLonAltBox.north = districtPlan.districtList[districtID].boundingBox.northWest.latitude.ToString(LatLongFormatString);
                    placemark.ExtendedData.latLonAltBox.west = districtPlan.districtList[districtID].boundingBox.northWest.longitude.ToString(LatLongFormatString);
                    placemark.ExtendedData.latLonAltBox.south = districtPlan.districtList[districtID].boundingBox.southEast.latitude.ToString(LatLongFormatString);
                    placemark.ExtendedData.latLonAltBox.east = districtPlan.districtList[districtID].boundingBox.southEast.longitude.ToString(LatLongFormatString);

                    // write district center point
                    placemark.ExtendedData.centerPoint = new kmlPoint();
                    placemark.ExtendedData.centerPoint.coordinates = districtPlan.districtList[districtID].centerPoint.longitude.ToString(LatLongFormatString) + "," + districtPlan.districtList[districtID].centerPoint.latitude.ToString(LatLongFormatString);

                    // write subShapeList
                    if (districtPlan.districtList[districtID].subShapeMetadataList.Count > 0)
                    {
                        placemark.ExtendedData.SubShapeList = new kmlDocumentPlacemarkExtendedDataSubShapeList[districtPlan.districtList[districtID].subShapeMetadataList.Count];
                        int subShapeIndex = 0;
                        foreach (var subShape in districtPlan.districtList[districtID].subShapeMetadataList)
                        {
                            placemark.ExtendedData.SubShapeList[subShapeIndex] = new kmlDocumentPlacemarkExtendedDataSubShapeList();
                            placemark.ExtendedData.SubShapeList[subShapeIndex].latLonAltBox = new kmlLatLonAltBox();
                            placemark.ExtendedData.SubShapeList[subShapeIndex].latLonAltBox.north = subShape.boundingBox.northWest.latitude.ToString(LatLongFormatString);
                            placemark.ExtendedData.SubShapeList[subShapeIndex].latLonAltBox.west = subShape.boundingBox.northWest.longitude.ToString(LatLongFormatString);
                            placemark.ExtendedData.SubShapeList[subShapeIndex].latLonAltBox.south = subShape.boundingBox.southEast.latitude.ToString(LatLongFormatString);
                            placemark.ExtendedData.SubShapeList[subShapeIndex].latLonAltBox.east = subShape.boundingBox.southEast.longitude.ToString(LatLongFormatString);
                            subShapeIndex++;
                        }
                    }
                    // write holeList
                    if (districtPlan.districtList[districtID].subShapeHoleMetadataList.Count > 0)
                    {
                        placemark.ExtendedData.holeList = new kmlDocumentPlacemarkExtendedDataSubShapeList[districtPlan.districtList[districtID].subShapeHoleMetadataList.Count];
                        int subShapeIndex = 0;
                        foreach (var holeShape in districtPlan.districtList[districtID].subShapeHoleMetadataList)
                        {
                            placemark.ExtendedData.holeList[subShapeIndex] = new kmlDocumentPlacemarkExtendedDataSubShapeList();
                            placemark.ExtendedData.holeList[subShapeIndex].latLonAltBox = new kmlLatLonAltBox();
                            placemark.ExtendedData.holeList[subShapeIndex].latLonAltBox.north = holeShape.boundingBox.northWest.latitude.ToString(LatLongFormatString);
                            placemark.ExtendedData.holeList[subShapeIndex].latLonAltBox.west = holeShape.boundingBox.northWest.longitude.ToString(LatLongFormatString);
                            placemark.ExtendedData.holeList[subShapeIndex].latLonAltBox.south = holeShape.boundingBox.southEast.latitude.ToString(LatLongFormatString);
                            placemark.ExtendedData.holeList[subShapeIndex].latLonAltBox.east = holeShape.boundingBox.southEast.longitude.ToString(LatLongFormatString);
                            subShapeIndex++;
                        }
                    }

                    // write district geometry
                    placemark.MultiGeometry = new kmlDocumentPlacemarkMultiGeometry[geometryCount];

                    //NOTE: This assumes vertices contains only polygons - may be an invalid assumption

                    // we only have one multi-geometry per district
                    int multiGeometryIndex = 0;
                    placemark.MultiGeometry[multiGeometryIndex] = new kmlDocumentPlacemarkMultiGeometry();
                    placemark.MultiGeometry[multiGeometryIndex].Polygon = new kmlDocumentPlacemarkMultiGeometryPolygon[districtPlan.districtList[districtID].districtMultiMapShape.Vertices.Count];

                    int polygonRingIndex = 0;
                    foreach (var ring in districtPlan.districtList[districtID].districtMultiMapShape.Vertices)
                    {
                        List<string> coordList = new List<string>();
                        foreach (var ringPoint in ring)
                        {
                            coordList.Add(ringPoint.Longitude.ToString(LatLongFormatString) + "," + ringPoint.Latitude.ToString(LatLongFormatString));
                        }


                        placemark.MultiGeometry[multiGeometryIndex].Polygon[polygonRingIndex] = new kmlDocumentPlacemarkMultiGeometryPolygon();
                        placemark.MultiGeometry[multiGeometryIndex].Polygon[polygonRingIndex].outerBoundaryIs = new kmlDocumentPlacemarkMultiGeometryPolygonOuterBoundaryIs();
                        placemark.MultiGeometry[multiGeometryIndex].Polygon[polygonRingIndex].outerBoundaryIs.linearRing = new kmlDocumentPlacemarkMultiGeometryPolygonOuterBoundaryIsLinearRing();
                        placemark.MultiGeometry[multiGeometryIndex].Polygon[polygonRingIndex].outerBoundaryIs.linearRing.coordinates = string.Join(" ", coordList);

                        polygonRingIndex++;
                    }

                    placemark.MultiGeometry[multiGeometryIndex].Point = new kmlPoint[1];
                    placemark.MultiGeometry[multiGeometryIndex].Point[0] = new kmlPoint();
                    placemark.MultiGeometry[multiGeometryIndex].Point[0].coordinates = districtPlan.districtList[districtID].centerPoint.longitude.ToString(LatLongFormatString) + "," + districtPlan.districtList[districtID].centerPoint.latitude.ToString(LatLongFormatString) + ",0";
                    placemark.MultiGeometry[multiGeometryIndex].Point[0].name = "d=" + districtID.ToString();

                    newKml.Items[0].Placemark[districtID - 1] = placemark;
                }
            }

            return newKml;

        }
        public static DistrictPlanInfoDTO ExtractPlanInfo( kml newKml)
        {
            DistrictPlanInfoDTO planInfo = new DistrictPlanInfoDTO();

            
                    if (newKml != null)
                    {
                        if (newKml.Items != null)
                        {
                            if (newKml.Items.Length > 0)
                            {
                                planInfo.planName = newKml.Items[0].name;
                                planInfo.districtCount = Int32.Parse(newKml.Items[0].NumberofDistricts);
                                planInfo.planTypeID = newKml.Items[0].plantype;
                                planInfo.planTypeName = newKml.Items[0].plantype;
                                    }
                        }
                    }

                    return planInfo;
        }

        public static bool ExtractKML(this DistrictPlan districtPlan, kml newKml)
        {
            //todo: protect against parsing exceptions
            if (newKml.Items == null)
            {
                //todo: KML Import: What to do if kml file placemark array is null?
                throw new Exception("Unable to parse items from KML file");
            }
            else
            {
                if (newKml.Items[0].name == null)
                {
                    newKml.Items[0].name = "";
                }
                else
                {
                    districtPlan.districtPlanName = newKml.Items[0].name;
                }

                if (newKml.Items[0].plantype == null)
                {
                    throw new Exception("Invalid KML file format - unable to parse plan type");
                }
                else
                {
                    districtPlan.districtPlanTypeName = newKml.Items[0].plantype;
                    districtPlan.districtPlanTypeID = districtPlan.districtPlanTypeName;  //TODO: convert this to look up planTypeID 
                }

                if (newKml.Items[0].description == null)
                {
                    districtPlan.districtPlanDescription = "";
                }
                else
                {
                    districtPlan.districtPlanDescription = newKml.Items[0].description;
                }

                if (newKml.Items[0].NumberofDistricts == null)
                {
                    throw new Exception("Invalid KML file format - unable to parse district count");
                }
                else
                {
                    try
                    {
                        districtPlan.districtCount = Int32.Parse(newKml.Items[0].NumberofDistricts);
                    }
                    catch
                    {
                        throw new Exception("Invalid KML file format - unable to convert district count to a number");
                    }

                }


                


                if (newKml.Items[0].Placemark == null)
                {
                    //todo: KML Import: What to do if kml file placemark array is null?
                    MessageBox.Show("There are no districts defined in this file, empty plan loaded");
                }
                else
                {
                    foreach (var loadedDistrict in newKml.Items[0].Placemark)
                    {
                        int districtID = Int32.Parse(loadedDistrict.name);

                        // fill with data
                        districtPlan.districtList[districtID].districtID = districtID;
                        districtPlan.districtList[districtID].displayName = districtID.ToString();

                        // initialize extended data
                        districtPlan.districtList[districtID].hasHole = false;
                        districtPlan.districtList[districtID].isSelected = false;
                        districtPlan.districtList[districtID].isLocked = false;
                        districtPlan.districtList[districtID].isContiguous = true;

                        districtPlan.districtList[districtID].boundingBox = BuildBoundingBox("0.0", "0.0", "0.0", "0.0");
                        districtPlan.districtList[districtID].centerPoint = new PointDTO();
                        districtPlan.districtList[districtID].centerPoint.latitude = 1.0;
                        districtPlan.districtList[districtID].centerPoint.longitude = -1.0;
                        districtPlan.districtList[districtID].subShapeMetadataList = new List<DistrictSubShapeMetadata>();
                        districtPlan.districtList[districtID].subShapeHoleMetadataList = new List<DistrictSubShapeMetadata>();
                        districtPlan.districtList[districtID].districtMultiMapShape = new MapMultiGeometry();
                        districtPlan.districtList[districtID].districtMultiMapShape.Vertices = new List<LocationCollection>();

                        if (loadedDistrict.ExtendedData == null)
                        {
                            //todo: KML Import: what to do if district extended data shows up as null?
                            throw new Exception("District [" + districtID.ToString() + "] has no extendedData, cannot load KML file");
                        }
                        else
                        {

                            districtPlan.districtList[districtID].hasHole = loadedDistrict.ExtendedData.hasHole;
                            districtPlan.districtList[districtID].isSelected = loadedDistrict.ExtendedData.isSelected;
                            districtPlan.districtList[districtID].isLocked = loadedDistrict.ExtendedData.isLocked;
                            districtPlan.districtList[districtID].isContiguous = loadedDistrict.ExtendedData.isContiguous;

                            if (loadedDistrict.ExtendedData.targetPopulation == 0)
                            {
                                // will use default target population if not provided
                            }
                            else
                            {
                                districtPlan.districtList[districtID].targetPopulation = loadedDistrict.ExtendedData.targetPopulation;
                            }

                            if (loadedDistrict.ExtendedData.latLonAltBox == null)
                            {
                                //todo: KML Import: What to do if district bounding box is null?
                                throw new Exception("District [" + districtID.ToString() + "] has no boundingBox, cannot load KML file");
                            }
                            else
                            {
                                districtPlan.districtList[districtID].boundingBox = BuildBoundingBox(loadedDistrict.ExtendedData.latLonAltBox.north, loadedDistrict.ExtendedData.latLonAltBox.west, loadedDistrict.ExtendedData.latLonAltBox.south, loadedDistrict.ExtendedData.latLonAltBox.east);
                            }

                            // centerpoint
                            if (loadedDistrict.ExtendedData.centerPoint == null)
                            {
                                //todo: KML Import: What to do if district centerpoint is null?
                                throw new Exception("District [" + districtID.ToString() + "] has no centerpoint, cannot load KML file");

                            }
                            else
                            {
                                string[] centerCoordinates = loadedDistrict.ExtendedData.centerPoint.coordinates.Split(',');
                                if (centerCoordinates.Length != 2)
                                {
                                    //todo: KML Import: What to do if district centerpoint doesn't convert to two numbers?
                                    throw new Exception("District [" + districtID.ToString() + "] has an invalid centerpoint, cannot load KML file");

                                }
                                else
                                {
                                    districtPlan.districtList[districtID].centerPoint.longitude = double.Parse(centerCoordinates[0]);
                                    districtPlan.districtList[districtID].centerPoint.latitude = double.Parse(centerCoordinates[1]);
                                }
                            }

                            // sub shapes
                            if (loadedDistrict.ExtendedData.SubShapeList != null)
                            {
                                foreach (var subShape in loadedDistrict.ExtendedData.SubShapeList)
                                {
                                    districtPlan.districtList[districtID].subShapeMetadataList.Add(BuildMetaData(subShape.latLonAltBox.north, subShape.latLonAltBox.west, subShape.latLonAltBox.south, subShape.latLonAltBox.east, Constants.StragglerIconUriString));
                                }
                            }

                            // holes
                            if (loadedDistrict.ExtendedData.holeList != null)
                            {
                                foreach (var hole in loadedDistrict.ExtendedData.holeList)
                                {
                                    districtPlan.districtList[districtID].subShapeHoleMetadataList.Add(BuildMetaData(hole.latLonAltBox.north, hole.latLonAltBox.west, hole.latLonAltBox.south, hole.latLonAltBox.east, Constants.HoleIconUriString));
                                }
                            }
                        }

                        // build the district shape
                        if (loadedDistrict.MultiGeometry != null)
                        {

                            foreach (var multiGeom in loadedDistrict.MultiGeometry)
                            {
                                // ignore the multiGeom point

                                if (multiGeom.Polygon == null)
                                {
                                    //todo: KML Import: What to do if polygon of district is null?
                                    throw new Exception("District [" + districtID.ToString() + "] has no polygon in its shape, cannot load KML file");

                                }
                                else
                                {
                                    foreach (var poly in multiGeom.Polygon)
                                    {
                                        LocationCollection locCol = new LocationCollection();
                                        string shapeCoords = poly.outerBoundaryIs.linearRing.coordinates;
                                        string[] locList = shapeCoords.Split(' ');
                                        foreach (var coordStr in locList)
                                        {
                                            string[] locStr = coordStr.Split(',');
                                            double lon = double.Parse(locStr[0]);
                                            double lat = double.Parse(locStr[1]);
                                            Location loc = new Location(lat, lon);
                                            locCol.Add(loc);

                                        }
                                        districtPlan.districtList[districtID].districtMultiMapShape.Vertices.Add(locCol);

                                    }
                                }
                                
                                //todo:  KML Import: Need to handle kml interior rings and multi polygons
                            }
                        }
                        else if (loadedDistrict.Polygon != null)
                        {
                            //todo: Import only a polygon, there is no multi geometry
                            throw new Exception("Import polygon not implemented");
                        }
                        else
                        {
                            //todo: KML Import: What to do if district plan multiGeometry is null?
                            throw new Exception("Unknown geometry in file, cannot load KML file");
                        }

                    }
                }
            }
            return true;
        }

        private static DistrictSubShapeMetadata BuildMetaData(string north, string west, string south, string east, string icon)
        {
            DistrictSubShapeMetadata newMetaData = new DistrictSubShapeMetadata();
            newMetaData.boundingBox = BuildBoundingBox(north, west, south, east);
            newMetaData.uriString = icon;
            return newMetaData;
        }

        private static BoundingBoxDTO BuildBoundingBox(string north, string west, string south, string east)
        {
            BoundingBoxDTO boundingBox = new BoundingBoxDTO();
            boundingBox.northWest = new PointDTO();
            boundingBox.northWest.latitude = double.Parse(north);
            boundingBox.northWest.longitude = double.Parse(west);
            boundingBox.southEast = new PointDTO();
            boundingBox.southEast.latitude = double.Parse(south);
            boundingBox.southEast.longitude = double.Parse(east);
            return boundingBox;
        }

        private static Location BuildPoint(string lat, string lon)
        {
            Location centerPoint = new Location(double.Parse(lat), double.Parse(lon));
            return centerPoint;
        }
    }
}
